﻿using Bit.Art.Account;
using Bit.Art.Api.Controllers;
using Bit.Art.Api.Models;
using Bit.Art.Core.Models;
using Bit.Art.Db;
using Bit.Art.Distributed;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using Bit.Art.Core.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NetMQ.Sockets;
using NetMQ;
using Bit.Art.Core;

namespace bit.art.test
{
#if UNITTEST
    [TestClass]
    public class 撮合引擎测试
    {
        public static IConfigurationRoot Configuration { get; set; }
        private static Memcache s_Memcache;

        private static void InitializeConfiguration()
        {
            var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            Configuration = builder.Build();
            Utility.Configuration = Configuration;
            ApplicationLogging.LoggerFactory.AddConsole(Configuration.GetSection("Logging"));
        }

        [ClassInitialize]
        public static void 启动消息队列各个节点(TestContext context)
        {
            InitializeConfiguration();
            ApplicationLogging.LoggerFactory.AddConsole().AddDebug(LogLevel.Information);
            s_Memcache = Memcache.Create("localhost");

            try
            {
                Bit.Art.MySqlDb.Program.Setup();
                var services = new ServiceCollection();
                services.AddDbContext<ExchangeDb>(options =>
                    options.UseMySql(Configuration.GetConnectionString("ExchangeDb")), ServiceLifetime.Transient);
                services.AddDbContext<ExchangeConfigDb>(options =>
                    options.UseMySql(Configuration.GetConnectionString("ExchangeConfigDb")), ServiceLifetime.Transient);
                Bit.Art.Engine.Program.GlobalServiceProvider =
                Bit.Art.MySqlDb.Program.GlobalServiceProvider =
                Bit.Art.Bookeeper.Program.GlobalServiceProvider =
                Bit.Art.Account.Program.GlobalServiceProvider = services
                    .AddLogging()
                    .BuildServiceProvider();

                Bit.Art.Broker.Program.StartEngine();
                Bit.Art.Account.Program.StartEngine(s_Memcache);
                Bit.Art.Bookeeper.Program.StartEngine(s_Memcache);
                Bit.Art.Engine.Program.StartEngine(s_Memcache, 500, 100000);
                Bit.Art.MySqlDb.Program.StartEngine();
                // 等待1秒，确保所有节点都启动了
                Bit.Art.Broker.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Account.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Bookeeper.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.Engine.Program.s_autoResetEventForTest.WaitOne(1000);
                Bit.Art.MySqlDb.Program.s_autoResetEventForTest.WaitOne(1000);
            }
            catch
            {
                using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
                {
                    db.Database.ExecuteSqlCommand("DELETE FROM exchangedb.orders WHERE Id <> ''; " +
                        "DELETE FROM exchangedb.accountbalances WHERE Id <> '';");
                }
            }
        }

        [TestInitialize]
        public async Task TestInitialize()
        {
            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                DbInitializer.Intialize(db);
            }
            await s_Memcache.FlushDb();
            Bit.Art.Account.Program.LoadDataToCache(s_Memcache);
        }

        [TestCleanup]
        public void TestCleanup()
        {
            Bit.Art.Broker.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Account.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Bookeeper.Program.s_autoResetEventForTest.Reset();
            Bit.Art.Engine.Program.s_autoResetEventForTest.Reset();
            Bit.Art.MySqlDb.Program.s_autoResetEventForTest.Reset();

            using (var db = Bit.Art.MySqlDb.Program.GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                db.Database.ExecuteSqlCommand("DELETE FROM exchangedb.orders WHERE Id <> ''; " +
                    "DELETE FROM exchangedb.accountbalances WHERE Id <> '';");
            }

            using (var db = Bit.Art.Engine.Program.GlobalServiceProvider.GetRequiredService<ExchangeConfigDb>())
            {
                db.Database.ExecuteSqlCommand("DELETE FROM Markets WHERE Id > 5;");
            }
        }

        [TestMethod]
        public async Task 无需重启新增交易对()
        {
            var market = new Market()
            {
                Name = "EOS/ETH",
                IsEnabled = true,
                DepthInMemory = 100,
                OrderCapacityInMemory = 1000
            };

            using (var db = Bit.Art.Engine.Program.GlobalServiceProvider.GetRequiredService<ExchangeConfigDb>())
            {
                db.Markets.Add(market);
                db.SaveChanges();
            }

            using (var requestor = new RequestSocket($">{SiteConfiguration.ChangeMarketEndpoint}"))
            {
                requestor.SendFrame(market.ToJson());
                var ret = await Task.Run(() =>
                {
                    var res = requestor.ReceiveMultipartMessage();
                    var bytes = res.Last.Buffer;
                    return bytes[0];
                });

                Assert.AreEqual(0, ret);
            }

            var permissions = new List<UpdateBalancePermission>();
            var appid = "Test_Batch_Update_User_Balance";
            permissions.Add(new UpdateBalancePermission()
            {
                AppId = appid,
                Coin = "EOS",
                ThirdPartyId = 1,
                Permitted = true
            });
            permissions.Add(new UpdateBalancePermission()
            {
                AppId = appid,
                Coin = "ETH",
                ThirdPartyId = 1,
                Permitted = true
            });

            await s_Memcache.Set("thirdparty", permissions.ToArray().ToJson());

            // 可以存币
            IOptions<ServerOptions> option = Options.Create<ServerOptions>(new ServerOptions()
            {
                SsoOption = new SsoOption()
                {
                    ServerUrl = "http://localhost:5000",
                    SsoClientId = "artapi",
                    SsoClientSecret = "asimplepassword"
                }
            });
            var logger = ApplicationLogging.CreateLogger<ThirdPartyController>();
            var amount = 100m;
            var controller = new ThirdPartyController(option, s_Memcache,
                                                      logger as ILogger<ThirdPartyController>);
            var requests = new List<DepositRequest>();
            requests.Add(new DepositRequest()
            {
                Id = 1,
                Coin = "EOS",
                Amount = amount,
                UserId = TestConstants.DEFAULT_BID_USER_ID
            });
            requests.Add(new DepositRequest()
            {
                Id = 2,
                Coin = "ETH",
                Amount = amount,
                UserId = TestConstants.DEFAULT_BID_USER_ID
            });
            requests.Add(new DepositRequest()
            {
                Id = 3,
                Coin = "EOS",
                Amount = amount,
                UserId = TestConstants.DEFAULT_ASK_USER_ID
            });
            requests.Add(new DepositRequest()
            {
                Id = 4,
                Coin = "ETH",
                Amount = amount,
                UserId = TestConstants.DEFAULT_ASK_USER_ID
            });
            var response = await controller.Deposit(appid, requests.ToArray());
            Assert.IsTrue(response.Success);
            Bit.Art.MySqlDb.Program.s_autoResetEventForTest.WaitOne();

            var users = response.Result;
            Assert.AreEqual(amount, 
                            users.Single(b => string.Compare(b.Coin, "EOS", true) == 0 &&
                                         b.UserId == TestConstants.DEFAULT_ASK_USER_ID).Balance);
            Assert.AreEqual(amount, 
                            users.Single(b => string.Compare(b.Coin, "ETH", true) == 0 &&
                                         b.UserId == TestConstants.DEFAULT_ASK_USER_ID).Balance);
            Assert.AreEqual(amount,
                            users.Single(b => string.Compare(b.Coin, "EOS", true) == 0 &&
                                         b.UserId == TestConstants.DEFAULT_BID_USER_ID).Balance);
            Assert.AreEqual(amount,
                            users.Single(b => string.Compare(b.Coin, "ETH", true) == 0 &&
                                         b.UserId == TestConstants.DEFAULT_BID_USER_ID).Balance);
            // 可以交易
            decimal bidprice = 0.1m, bidvolume = 1m;
            var mcontroller = new MarketController();
            mcontroller.SetMemcache(s_Memcache);
            mcontroller._uid = TestConstants.DEFAULT_BID_USER_ID;
            var bidRes = await mcontroller.BuyLimit("EOS/ETH", bidprice, bidvolume);
            mcontroller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var askRes = await mcontroller.SellLimit("EOS/ETH", bidprice, bidvolume);
            Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);
            Bit.Art.Bookeeper.Program.s_filledAutoEventForTest.WaitOne(1000);

            mcontroller._uid = TestConstants.DEFAULT_ASK_USER_ID;
            var apiBalancesRes = await mcontroller.GetBalance();
            var apiBalances = apiBalancesRes.Result;
            var apiBalance = apiBalances.Single(b => b.Coin == "ETH");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(amount + bidprice * bidvolume * (1 - 0.001m), apiBalance.Available);
            Assert.AreEqual(amount + bidprice * bidvolume * (1 - 0.001m), apiBalance.Balance);
            apiBalance = apiBalances.Single(b => b.Coin == "EOS");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(amount - bidvolume, apiBalance.Available);
            Assert.AreEqual(amount - bidvolume, apiBalance.Balance);

            mcontroller._uid = TestConstants.DEFAULT_BID_USER_ID;
            apiBalancesRes = await mcontroller.GetBalance();
            apiBalances = apiBalancesRes.Result;
            apiBalance = apiBalances.Single(b => b.Coin == "ETH");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(amount - bidprice * bidvolume * (1 + 0.001m), apiBalance.Available);
            Assert.AreEqual(amount - bidprice * bidvolume * (1 + 0.001m), apiBalance.Balance);
            apiBalance = apiBalances.Single(b => b.Coin == "EOS");
            Assert.AreEqual(0, apiBalance.Pending);
            Assert.AreEqual(amount + bidvolume, apiBalance.Available);
            Assert.AreEqual(amount + bidvolume, apiBalance.Balance);
        }
    }
#endif
}
